﻿using System;
using System.Linq;

namespace FBGraph.ClientGenerator.CodeGen
{
    public class PropertyList : CodeGenObjectList<Property>
    {
        public PropertyList(CodeGenType containingType, params Property[] properties)
            : base(1, properties)
        {
            this.ContainingType = containingType;
        }

        public CodeGenType ContainingType { get; private set; }

        public Boolean Suggest(Property suggested)
        {
            var result = this.InnerSuggest(suggested);

            var existingInSubClasses = (
                from sub in this.ContainingType.SubClasses
                from prop in sub.Properties
                where prop.Name == suggested.Name
                select new { SubClass = sub, Property = prop }).ToArray();

            foreach (var existing in existingInSubClasses)
            {
                this.InnerSuggest(existing.Property);
                existing.SubClass.Properties.Remove(existing.Property);
            }

            return result;
        }

        private Boolean InnerSuggest(Property suggested)
        {
            // todo: check subclasses for existing property

            if (this.ContainingType.BaseType != null &&
                this.ContainingType.BaseType.IsGeneratedType &&
                this.ContainingType.BaseType.GeneratedType.Properties.Any(p => p.Name == suggested.Name))
            {
                return this.ContainingType.BaseType.GeneratedType.Properties.Suggest(suggested);                
            }
            else
            {
                var existing = this.SingleOrDefault(p => p.Name == suggested.Name);

                if (existing == null)
                {   // new property, add it
                    this.Add(suggested);
                    return true;
                }
                else
                {
                    // merge comments
                    foreach (var comment in suggested.XmlDocComments) existing.XmlDocComments.Suggest(comment);

                    if (existing.BaseType.Equals(suggested.BaseType)) return false;
                    else if (existing.ExplicitImplementationType != null || suggested.ExplicitImplementationType != null)
                    {
                        if (existing.ExplicitImplementationType != null && suggested.ExplicitImplementationType != null)
                        {
                            if (existing.ExplicitImplementationType.Name != suggested.ExplicitImplementationType.Name)
                            {   // member and existing member are private implementations for different interfaces, add it
                                this.Add(suggested);
                                return true;
                            }
                            else if (existing.ExplicitImplementationType.TypeArguments.SequenceEqual(suggested.ExplicitImplementationType.TypeArguments))
                            {   // exact implementation already exists, ignore it
                                return false;
                            }
                            else if (existing.ExplicitImplementationType.TypeArguments.Count != suggested.ExplicitImplementationType.TypeArguments.Count)
                            {   // interfaces have different generic signatures, add it
                                this.Add(suggested);
                                return true;
                            }
                            else if (existing.ExplicitImplementationType.TypeArguments.Count == 1)
                            {
                                if (existing.ExplicitImplementationType.TypeArguments[0].Type == typeof(String))
                                {   // use the more specific suggestion, replace existing with suggestion
                                    this.Remove(existing);
                                    this.Add(suggested);
                                    return true;
                                }
                                else if (suggested.ExplicitImplementationType.TypeArguments[0].Type == typeof(String))
                                {   // keep the more specific suggestion, ignore it
                                    return false;
                                }
                                else
                                {   // use the more recent suggestion, replace existing with suggestion
                                    this.Remove(existing);
                                    this.Add(suggested);
                                    return true;
                                }
                            }
                            else
                            {   // don't know what to do here yet, but it shouldn't happen in the current configuration
                                throw new InvalidOperationException();
                            }
                        }
                        else
                        {   // one has an explicit implementation, the other does not, so add it
                            this.Add(suggested);
                            return true;
                        }
                    }
                    else
                    {
                        var makeNullable = false;
                        var useExisting = false;

                        if (existing.BaseType.Type == typeof(Object)) useExisting = false;
                        // String is the lowest common denominator. If any iterations of the member resulted in a String type, no other type can be used
                        else if (existing.BaseType.Type == typeof(String)) useExisting = existing.BaseType.Type != null;
                        else if (suggested.BaseType.Type == typeof(String)) useExisting = existing.BaseType.Type == null;

                            // Boolean is the most specific non-generated type used - any other type would be more general
                        else if (existing.BaseType.Type == typeof(Boolean)) useExisting = false;
                        else if (existing.BaseType.Type == typeof(Decimal))
                        {
                            if (suggested.BaseType.Type == typeof(Int64)) useExisting = true;
                            else if (suggested.BaseType.Type == typeof(DateTime)) useExisting = false;
                            else if (suggested.BaseType.Type == typeof(Boolean)) useExisting = true;
                            else useExisting = true;
                        }
                        else if (existing.BaseType.Type == typeof(Int64))
                        {
                            if (suggested.BaseType.Type == typeof(Decimal)) useExisting = false;
                            else if (suggested.BaseType.Type == typeof(DateTime)) useExisting = false;
                            else if (suggested.BaseType.Type == typeof(Boolean)) useExisting = true;
                            else useExisting = true;
                        }
                        else if (existing.BaseType.Type == typeof(DateTime)) useExisting = true;
                        else useExisting = true;

                        if (useExisting) makeNullable = suggested.BaseType.Type == typeof(Object);
                        else makeNullable = existing.BaseType.Type == typeof(Object);

                        if (useExisting)
                        {
                            if (makeNullable) existing.MakeNullable();
                            return false;
                        }
                        else
                        {
                            if (makeNullable) suggested.MakeNullable();
                            this.Remove(existing);
                            this.Add(suggested);
                            return true;
                        }
                    }
                }
            }
        }
    }
}